# -*- coding: utf-8 -*-

from ir.ir_ds import *
from ir.ir_helper import Helper

from hbcxx.cxx_format import *
from hbcxx.simcxx_types import *
from hbcxx.simcxx_pack import simcxx_create_struct


def simcxx_init_port(port_type):
    if isinstance(port_type, ITypePortOut):
        return 'new PortOut<{}>(this)'.format(simcxx_get_type(port_type.managed_type))
    if isinstance(port_type, ITypePortIn):
        return 'new PortIn<{}>(this, {})'.format(simcxx_get_type(port_type.managed_type),
                                                     port_type.max_size)
    if isinstance(port_type, ITypeLanePortSender):
        return 'new {}(this)'.format(simcxx_remove_pointer_type(simcxx_get_type(port_type)))
    if isinstance(port_type, ITypeLanePortReceiver):
        return 'new {}(this)'.format(simcxx_remove_pointer_type(simcxx_get_type(port_type)))
    raise Exception('Unimplemented port initialization {}.'.format(type(port_type)))


def simcxx_convert_lane_sender(ir_lane_sender: ITypeLanePortSender):
    sender_name = ir_lane_sender.name
    h_def = ['class {} : public LanePortSender'.format(sender_name),
               '{',
               'public:']
    # Loop and generate the ports.
    for port_name in ir_lane_sender.ports:
        h_def.append(CXX_INDENT +
                       '{} {};'.format(simcxx_convert_port(ir_lane_sender.ports[port_name]),
                                       port_name))
    # First, we need the constructor of the paper.
    h_def.append('\n' + CXX_INDENT + '{}(JObject *parent = nullptr);'.format(sender_name))
    cpp_def = ['{}::{}(JObject *parent) :'.format(sender_name, sender_name)]
    initial_codes = CXX_INDENT + 'LanePortSender(parent)'
    for port_name in ir_lane_sender.ports:
        initial_codes += ',\n' + CXX_INDENT + '{}({})'.format(port_name,
                                            simcxx_init_port(ir_lane_sender.ports[port_name]))
    initial_codes += '\n{\n}\n'
    cpp_def.append(initial_codes)
    # We need to add 'connect()' method of the port.
    lane_name = sender_name[:-len('Sender')]
    receiver_name = lane_name + 'Receiver'
    h_def += [CXX_INDENT + 'void connect({} *sender);'.format(receiver_name)]
    cpp_def += ['void {}::connect({} *sender)'.format(sender_name, receiver_name),
                '{']
    for port_name in ir_lane_sender.ports:
        cpp_def.append(CXX_INDENT + '{}->connect(sender->{});'.format(port_name, port_name))
    cpp_def.append('}\n')
    # Also, a 'put()' method is necessary.
    put_params = []
    for port_name in ir_lane_sender.ports:
        put_params.append([simcxx_get_type(ir_lane_sender.ports[port_name].managed_type),
                           'data_{}'.format(port_name)])
    put_params = ', '.join('{} {}'.format(p_type, p_name) for p_type, p_name in put_params)
    h_def += [CXX_INDENT + 'void put({});'.format(put_params)]
    cpp_def += ['void {}::put({})'.format(sender_name, put_params),
                '{']
    for port_name in ir_lane_sender.ports:
        cpp_def.append(CXX_INDENT + '{}->put(data_{});'.format(port_name, port_name))
    cpp_def.append('}\n')
    # Add the 'put_bypass' function.
    h_def += [CXX_INDENT + 'void put_bypass({} *receiver);'.format(receiver_name)]
    cpp_def += ['void {}::put_bypass({} *receiver)'.format(sender_name, receiver_name),
                '{']
    # Loop and construct the put operations.
    for port_name in ir_lane_sender.ports:
        cpp_def.append(CXX_INDENT + '{}->put(receiver->{}->get());'.format(port_name, port_name))
    cpp_def.append('}\n')
    # And we need 'is_any_full()'.
    h_def += [CXX_INDENT + 'bool is_any_full();']
    cpp_def += ['bool {}::is_any_full()'.format(sender_name),
                '{',
                CXX_INDENT + 'return {};'.format(' || '.join('{}->is_full()'.format(port_name) for port_name in ir_lane_sender.ports)),
                '}']
    # Complete the header definition.
    h_def.append('};')
    # Create the port classdef.
    return '\n'.join(h_def), '\n'.join(cpp_def)


def simcxx_convert_lane_receiver(ir_lane_receiver: ITypeLanePortReceiver):
    receiver_name = ir_lane_receiver.name
    h_def = ['class {} : public LanePortReceiver'.format(receiver_name),
             '{',
             'public:']
    # Loop and generate the ports.
    for port_name in ir_lane_receiver.ports:
        h_def.append(CXX_INDENT +
                       '{} {};'.format(simcxx_convert_port(ir_lane_receiver.ports[port_name]),
                                       port_name))
    # First, we need the constructor of the paper.
    h_def.append('\n' + CXX_INDENT + '{}(JObject *parent = nullptr);'.format(receiver_name))
    cpp_def = ['{}::{}(JObject *parent) :'.format(receiver_name, receiver_name)]
    initial_codes = CXX_INDENT + 'LanePortReceiver(parent)'
    for port_name in ir_lane_receiver.ports:
        initial_codes += ',\n' + CXX_INDENT + '{}({})'.format(
            port_name, simcxx_init_port(ir_lane_receiver.ports[port_name]))
    initial_codes += '\n{\n}\n'
    cpp_def.append(initial_codes)
    # We need to add 'get()' method of the port.
    lane_name = receiver_name[:-len('Receiver')]
    pack_name = lane_name + 'Pack'
    h_def.append(CXX_INDENT + '{} get();'.format(pack_name))
    pack_var_name = 'tmp_{}'.format(id(ir_lane_receiver))
    cpp_def += ['{} {}::get()'.format(pack_name, receiver_name),
                '{',
                CXX_INDENT + '{} {};'.format(pack_name, pack_var_name)]
    for port_name in ir_lane_receiver.ports:
        cpp_def.append(CXX_INDENT + '{}.{} = {}->get();'.format(pack_var_name, port_name, port_name))
    cpp_def += [CXX_INDENT + 'return {};'.format(pack_var_name),
                '}\n']
    # We need to add empty and full checking.
    h_def.append(CXX_INDENT + 'bool is_any_empty();')
    cpp_def += ['bool {}::is_any_empty()'.format(receiver_name),
                '{',
                CXX_INDENT + 'return {};'.format(' || '.join('{}->is_empty()'.format(port_name) for port_name in ir_lane_receiver.ports)),
                '}\n']
    h_def.append(CXX_INDENT + 'bool is_any_full();')
    cpp_def += ['bool {}::is_any_full()'.format(receiver_name),
                '{',
                CXX_INDENT + 'return {};'.format(' || '.join('{}->is_full()'.format(port_name) for port_name in ir_lane_receiver.ports)),
                '}']
    # Complete the header definition.
    h_def.append('};')
    # Create the port class def.
    return '\n'.join(h_def), '\n'.join(cpp_def)


def simcxx_convert_channel_sender(ir_channel_sender: ITypeChannelPortSender):
    sender_name = ir_channel_sender.name
    h_def = ['class {} : public ChannelPortSender'.format(sender_name),
             '{',
             'public:']
    # Add Port initialization.
    for port_name in ir_channel_sender.ports:
        h_def.append(CXX_INDENT +
                     '{} {};'.format(simcxx_convert_port(ir_channel_sender.ports[port_name]),
                                      port_name))
    h_def.append('\n'+ CXX_INDENT + '{}(JObject *parent);'.format(sender_name))
    cpp_def = ['{}::{}(JObject *parent) :'.format(sender_name, sender_name)]
    initial_codes = CXX_INDENT + 'ChannelPortSender(parent)'
    for port_name in ir_channel_sender.ports:
        initial_codes += ',\n ' + CXX_INDENT + '{}({})'.format(
            port_name, simcxx_init_port(ir_channel_sender.ports[port_name]))
    initial_codes += '\n{\n}'
    cpp_def.append(initial_codes)
    h_def.append('};')
    return '\n'.join(h_def), '\n'.join(cpp_def)


def simcxx_convert_channel_receiver(ir_channel_receiver: ITypeChannelPortReceiver):
    receiver_name = ir_channel_receiver.name
    h_def = [
        'class {} : public ChannelPortReceiver'.format(receiver_name),
        '{',
        'public:']
    # Add Port initialization.
    for port_name in ir_channel_receiver.ports:
        h_def.append(CXX_INDENT +
                       '{} {};'.format(simcxx_convert_port(ir_channel_receiver.ports[port_name]),
                                       port_name))
    # Create constructor.
    h_def.append('\n' + CXX_INDENT + '{}(JObject *parent);'.format(receiver_name))
    cpp_def = ['{}::{}(JObject *parent) :'.format(receiver_name, receiver_name)]
    initial_codes = CXX_INDENT + 'ChannelPortReceiver(parent)'
    for port_name in ir_channel_receiver.ports:
        initial_codes += ',\n' + CXX_INDENT + '{}({})'.format(
            port_name, simcxx_init_port(ir_channel_receiver.ports[port_name]))
    initial_codes += '\n{\n}'
    cpp_def.append(initial_codes)
    h_def.append('};')
    return '\n'.join(h_def), '\n'.join(cpp_def)


def simcxx_convert_channel(ir_channel: ITypeChannel, helper: Helper):
    # Get the sender and receiver port.
    channel_name = ir_channel.name
    sender_name = '{}Sender'.format(channel_name)
    receiver_name = '{}Receiver'.format(channel_name)
    # Check channel port type.
    h_def = ['class {} : public Channel'.format(channel_name),
             '{',
             'public:',
             CXX_INDENT + 'static void connect({} *sender, {} *receiver);'.format(sender_name, receiver_name),
             '};']
    cpp_def = ['void {}::connect({} *sender, {} *receiver)'.format(channel_name, sender_name, receiver_name),
               '{']
    # Find helper of sender and receiver.
    sender_types = helper.types[sender_name]
    # Check the sender port types.
    for port_name in sender_types.ports:
        port_type = sender_types.ports[port_name]
        # Check the type.
        if isinstance(port_type, ITypeLanePortSender):
            # Sender connect to receiver.
            cpp_def.append(CXX_INDENT + 'sender->{}->connect(receiver->{});'.format(port_name, port_name))
        else:
            # Receiver connect to sender.
            cpp_def.append(CXX_INDENT + 'receiver->{}->connect(sender->{});'.format(port_name, port_name))
    cpp_def.append('}')
    return '\n'.join(h_def), '\n'.join(cpp_def)


def simcxx_convert_channels(channel_types: list, helper: Helper):
    h_codes_struct = []
    h_codes_lane_receviver = []
    h_codes_lane_sender = []
    h_codes_channel_receiver = []
    h_codes_channel_sender = []
    h_codes_channel = []
    cpp_codes = []
    # Loop and define all the types.
    for ir_typedef in channel_types:
        if isinstance(ir_typedef, ITypeLanePortSender):
            h_code, cpp_code = simcxx_convert_lane_sender(ir_typedef)
            h_codes_lane_sender.append(h_code)
            cpp_codes.append(cpp_code)
        elif isinstance(ir_typedef, ITypeLanePortReceiver):
            h_code, cpp_code = simcxx_convert_lane_receiver(ir_typedef)
            h_codes_lane_receviver.append(h_code)
            cpp_codes.append(cpp_code)
        elif isinstance(ir_typedef, ITypeStruct):
            h_codes_struct.append(simcxx_create_struct(ir_typedef))
        elif isinstance(ir_typedef, ITypeChannelPortSender):
            h_code, cpp_code = simcxx_convert_channel_sender(ir_typedef)
            h_codes_channel_sender.append(h_code)
            cpp_codes.append(cpp_code)
        elif isinstance(ir_typedef, ITypeChannelPortReceiver):
            h_code, cpp_code = simcxx_convert_channel_receiver(ir_typedef)
            h_codes_channel_receiver.append(h_code)
            cpp_codes.append(cpp_code)
        elif isinstance(ir_typedef, ITypeChannel):
            h_code, cpp_code = simcxx_convert_channel(ir_typedef, helper)
            h_codes_channel.append(h_code)
            cpp_codes.append(cpp_code)
        else:
            raise Exception('Unimplemented typedef {}'.format(type(ir_typedef)))
    # Combine the header codes.
    h_codes = h_codes_struct + h_codes_lane_receviver + h_codes_lane_sender + \
        h_codes_channel_receiver + h_codes_channel_sender + h_codes_channel
    # Check the lane state.
    need_port = False
    if len(h_codes_lane_receviver) > 0 or len(h_codes_lane_sender) > 0:
        simcxx_add_include('"lane.h"')
        need_port = True
    if len(h_codes_channel_sender) > 0 or len(h_codes_channel_receiver) > 0:
        simcxx_add_include('"channel.h"')
        need_port = True
    if need_port or len(h_codes) > 0:
        simcxx_add_include('"port.h"')
    return h_codes, cpp_codes